package calendar;

import edu.gmu.server.RmiSkeleton;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;

/* this class manages all the calendars and allows creation and listing */
public class CalendarMgr implements CalendarMgrInt, Serializable
{
  private static final long serialVersionUID = 3390578935L;
  
  Registry registry;
  HashMap calendars;
  String host;
  int port;
  String type;
  boolean replicaConnected;
  boolean replicaChecked;
  transient CalendarMgrInt replica;
  transient String replicaHost;
  transient int replicaPort;
  
  public CalendarMgr()
  {
    super();
    calendars = new HashMap();
    replicaConnected = false;
    replicaChecked = false;
  }

  public void setParams( String host, int port, String type, String replicaHost, int replicaPort )
  {
    this.host = host;
    this.port = port;
    this.type = type;
    this.replicaHost = replicaHost;
    this.replicaPort = replicaPort;
    System.out.println( "Using type: " + this.type );
  }

  public boolean connectReplica()
  {
    if ( !replicaConnected && !this.type.equals("PrimaryElect") )
    {
      try
      {
        replica = new CalendarMgrStub( this.replicaHost, this.replicaPort, 
          this.replicaPort, this.type );
        if ( this.type.equals( "Primary" ) )
          System.out.println( "Connecting to Backup calendar manager." );
        else if(this.type.equals("Backup"))
          System.out.println( "Connecting to Primary calendar manager." );
        replicaConnected = true;
        //System.out.println( "Connecting manager with port: " + this.replicaPort);
      }
      catch (Exception e)
      {
        if ( this.type.equals( "Primary" ) && !replicaChecked )
          System.out.println( "Cannot connect to Backup calendar manager." );
        else if(this.type.equals("Backup") && !replicaChecked )
          System.out.println( "Cannot connect to Primary calendar manager." );
        replicaChecked = true;
        return false;
      }
    }

    /* tell all the calendars to connect to their replicas */
    CalendarObj calendar = null;
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      this.replicaPort+=2;
      if ( calendar != null ) calendar.connectReplica( this.replicaHost,
        this.replicaPort);
    }

    return pingReplica();
  }

  // check for existence of replica manager
  public boolean pingReplica()
  {
    if ( replicaConnected )
    {
      try {
        replica.isAlive();
      } catch (IOException ex) {
        if ( this.type.equals("Backup") )
        {
          type="PrimaryElect";
          System.out.println( "Server: Lost connection to Primary" );
          System.out.println( "Server: Backup promoted to PrimaryElect" );

          CalendarObj calendar = null;
          /* use a map to track calendars that have been disconnected from */
          Set set = calendars.entrySet();
          Iterator itr = set.iterator();
          while ( itr.hasNext() )
          {
            Map.Entry entry = (Map.Entry)itr.next();
            calendar = (CalendarObj)entry.getKey();
            if ( calendar != null )
            {
              calendar.promote();
            }
          }
        }
        else if ( this.type.equals("Primary") )
        {
          System.out.println( "Server: Lost connection to Backup" );
        }
        replicaConnected = false;
        replicaChecked = false;
        return false;
      }
      return true;
    }
    else return false;
  }

  // dummy method for responding to pingReplica
  public void isAlive() {}

  /* create a new calendar for the specified user */
  public synchronized boolean createCalendar( User user ) throws IOException
  {
    if ( replicaConnected && this.type.equals("Primary") )
      replica.createCalendar(user);
    
    CalendarObj calendar = null;

    if ( user.getName().compareTo("") == 0 ) return false;
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      if ( calendar != null )
      {
        if ( calendar.getUser().getName().compareTo(user.getName()) == 0 )
          return false;
      }
    }

    calendar = new CalendarObj( user, this, this.type );
    calendars.put( (Object)calendar, (Object)new Boolean(false) );
    String strName = "Calendar" + calendar.getUser().getName();
    
    this.port+=2;
    user.setPort(this.port); /*not sure if this is working */
    CalendarSkeleton stub = new CalendarSkeleton( calendar, this.port,
      this.port, strName );
    if ( stub == null )
    {
      System.out.println( "(ERROR) Failed to create CalendarSkeleton stub."
        + "Please try again." );
      calendars.remove( (Object)calendar );
      calendar = null;
      user.setPort( 0 );
      return false;
    }
    calendar.setCallback( stub );
//    System.out.println( "Creating calendar object skeleton on " + this.port );

    /* notify all connected clients that a new calendar has been created */
    set = calendars.entrySet();
    itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      if ( calendar != null && this.type.indexOf( "Primary" ) > -1 )
      {
        calendar.notifyClientUser( user, this.list() );
      }
    }
    this.writeToDisk();
    return true;
  }

  /* connect to the calendar specified by the user id */
  public synchronized boolean connectCalendar( User user )
    throws IOException
  {
    CalendarObj calendar = null;
    /* use a map to track calendars that have been connected to */
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      if ( calendar != null && calendar.getUser().getId() == user.getId() )
      {
        if ( !((Boolean)entry.getValue()).booleanValue() ) {
          calendar.setIsConnected( true );
          entry.setValue( (Object)new Boolean( true ) );
          return true;
        }
      }
    }
    this.writeToDisk();
    return false;
  }

  /* disconnect from the calendar specified by the user id */
  public synchronized boolean disconnectCalendar( User user )
    throws IOException
  {
    CalendarObj calendar = null;
    /* use a map to track calendars that have been disconnected from */
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      if ( calendar != null && calendar.getUser().getId() == user.getId() )
      {
        if ( ((Boolean)entry.getValue()).booleanValue() ) {
          calendar.setIsConnected( false );
          entry.setValue( (Object)new Boolean( false ) );
          return true;
        }
      }
    }
    this.writeToDisk();
    return false;
  }

  /* list all the calendars (user) that have been created */
  public synchronized ArrayList list() throws IOException
  {
    CalendarObj calendar = null;
    ArrayList users = new ArrayList();
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      users.add( calendar.getUser() );
    }
    return users;
  }

  /* get the calendar object specified by the user id */
  public CalendarObj getCalendar( int userId ) throws IOException
  {
    CalendarObj calendar = null;
    Set set = calendars.entrySet();
    Iterator itr = set.iterator();
    while ( itr.hasNext() )
    {
      Map.Entry entry = (Map.Entry)itr.next();
      calendar = (CalendarObj)entry.getKey();
      if ( calendar != null && calendar.getUser().getId() == userId )
      {
        return calendar;
      }
    }

    return null;
  }

  /* reset all the temporary states in the manager (used for persistence) */
  public void reset()
  {
    replicaConnected = false;
    replicaChecked = false;
    if ( calendars.size() > 0 )
    {
      CalendarObj calendar = null;
      Set set = calendars.entrySet();
      Iterator itr = set.iterator();
      while ( itr.hasNext() )
      {
        Map.Entry entry = (Map.Entry)itr.next();
        calendar = (CalendarObj)entry.getKey();
        if ( calendar != null )
        {
          calendar.setIsConnected( false );
          calendar.setReplicaConnected( false );
          calendar.setReplicaChecked( false );
          entry.setValue( (Object)new Boolean( false ) );
          String strName = null;
          try
          {
            calendar.clearMembers();
            strName = "Calendar" + calendar.getUser();           
            System.out.println( "Server: Registering " + strName );
            this.port+=2;
            calendar.getUser().setPort(this.port);
            CalendarSkeleton stub = new CalendarSkeleton( calendar,
              this.port, this.port, strName );
          //  System.out.println( "Creating calendar object skeleton on " + this.port );
          }
          catch(IOException ex)
          {
            System.out.println("CalendarMgr: Failed to create calendar "
              + strName + " -> " + ex);
          }
        }
      }
    }
  }

  /* write out the calendar manager and all calendars to disk (persistence) */
  synchronized void writeToDisk()
  {
    try
    {
      if ( this.type.equals( "Primary" ) ) writePrimary();
      else if ( this.type.equals( "Backup" ) ) writeBackup();
      else if ( this.type.equals( "PrimaryElect" ) )
      {
        writePrimary();
        writeBackup();
      }
    }
    catch( Exception ex )
    {
      System.out.println( "CalendarMgr: writeToDisk -> " + ex );
    }
  }

  void writePrimary() throws FileNotFoundException, IOException
  {
    FileOutputStream file = null;
    file=new FileOutputStream("CalendarMgrPrimary.data");
    ObjectOutputStream out = new ObjectOutputStream( file );
    out.writeObject( this );
    out.close();
    file.close();
  }

  void writeBackup() throws FileNotFoundException, IOException
  {
    FileOutputStream file = null;
    file=new FileOutputStream("CalendarMgrBackup.data");
    ObjectOutputStream out = new ObjectOutputStream( file );
    out.writeObject( this );
    out.close();
    file.close();
  }
}